package cn.smallbun.screw.core.engine.excel;

import cn.smallbun.screw.core.Configuration;
import static cn.smallbun.screw.core.constant.DefaultConstants.DEFAULT_ENCODING;
import cn.smallbun.screw.core.engine.AbstractTemplateEngine;
import cn.smallbun.screw.core.engine.excel.bean.DBInfoExcelModel;
import cn.smallbun.screw.core.engine.excel.bean.TableContentExcelModel;
import cn.smallbun.screw.core.engine.excel.bean.TableFieldExcelModel;
import cn.smallbun.screw.core.engine.excel.bean.TableInfoExcelModel;
import cn.smallbun.screw.core.exception.ProduceException;
import cn.smallbun.screw.core.metadata.model.DataModel;
import cn.smallbun.screw.core.util.CollectionUtils;
import cn.smallbun.screw.core.util.ExceptionUtils;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.WriteTable;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import com.alibaba.excel.write.style.column.AbstractColumnWidthStyleStrategy;
import org.apache.poi.common.usermodel.HyperlinkType;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CreationHelper;
import org.apache.poi.ss.usermodel.Hyperlink;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.xssf.usermodel.XSSFFont;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * excel 写文件
 *
 * @author fanglei
 * @date 2021/08/10
 **/
public class ExcelTemplateEngine extends AbstractTemplateEngine {
    
    
    protected Configuration config;
    
    public ExcelTemplateEngine(Configuration config) {
        super(config.getEngineConfig());
        this.config = config;
    }
    
    @Override
    public void produce(DataModel dataModel, OutputStream outputStream) throws ProduceException {
        ExcelWriter excelWriter = null;
        try{
            excelWriter = EasyExcel.write(outputStream, DBInfoExcelModel.class)
                    .registerWriteHandler(new CustomColumnWidthhandler())
                    .registerWriteHandler(new CustomCellWriteHandler())
                    .build();
    
            WriteSheet writeSheet0 = EasyExcel.writerSheet("目录").needHead(Boolean.FALSE).build();
            
            // 数据库文档描述
            WriteTable writeTable1 = EasyExcel.writerTable(0)
                    .head(DBInfoExcelModel.class).needHead(Boolean.TRUE).build();
            // 表目录
            WriteTable writeTable2 = EasyExcel.writerTable(1)
                    .head(TableContentExcelModel.class).needHead(Boolean.TRUE).build();
            
            // 构建数据库文档配置信息
            List<DBInfoExcelModel> dbInfoExcelModels = new ArrayList<>(1);
            DBInfoExcelModel dbInfoExcelModel = new DBInfoExcelModel();
            dbInfoExcelModel.setDbName(dataModel.getDatabase());
            dbInfoExcelModel.setVersion(dataModel.getVersion());
            dbInfoExcelModel.setDescription(dataModel.getDescription());
            dbInfoExcelModels.add(dbInfoExcelModel);
            // 第一次写入会创建头
            excelWriter.write(dbInfoExcelModels, writeSheet0, writeTable1);
            
            // 处理目录中的表信息
            List<TableContentExcelModel> tableContentExcelModels = new ArrayList<>(dataModel.getTables().size());
            AtomicInteger integer = new AtomicInteger(1);
            dataModel.getTables().stream().forEach(c->{
                TableContentExcelModel tableContentExcelModel = new TableContentExcelModel();
                tableContentExcelModel.setNumberNo(integer.getAndIncrement());
                tableContentExcelModel.setTableName(c.getTableName());
                tableContentExcelModel.setRemark(c.getRemarks());
                tableContentExcelModels.add(tableContentExcelModel);
            });
            // 第一次写入会创建头
            excelWriter.write(tableContentExcelModels, writeSheet0, writeTable2);
            
            ExcelWriter finalExcelWriter = excelWriter;
            dataModel.getTables().forEach(c->{
                // 处理表信息
                List<TableInfoExcelModel> tableInfoExcelModels = new ArrayList<>();
                TableInfoExcelModel tableInfoExcelModel = TableInfoExcelModel.builder()
                        .tableName(c.getTableName()).remark(c.getRemarks()).build();
                tableInfoExcelModels.add(tableInfoExcelModel);
    
                WriteSheet writeSheet1 = EasyExcel.writerSheet(tableInfoExcelModel.getTableName()).needHead(Boolean.FALSE).build();
                WriteTable writeTable3 = EasyExcel.writerTable(0)
                        .head(TableInfoExcelModel.class).needHead(Boolean.TRUE).build();
                WriteTable writeTable4 = EasyExcel.writerTable(1)
                        .head(TableFieldExcelModel.class)
                        .needHead(Boolean.TRUE)
                        .build();
                // 第一次写入会创建头
                finalExcelWriter.write(tableInfoExcelModels, writeSheet1, writeTable3);
                List<TableFieldExcelModel> tableFieldExcelModels = new ArrayList<>();
                AtomicInteger atomicInteger = new AtomicInteger(1);
                c.getColumns().forEach(co -> {
                    TableFieldExcelModel tableFieldExcelModel = new TableFieldExcelModel();
                    tableFieldExcelModel.setNumberNo(atomicInteger.getAndIncrement());
                    tableFieldExcelModel.setCloumnName(co.getColumnName());
                    tableFieldExcelModel.setDataType(co.getTypeName());
                    tableFieldExcelModel.setDataLength(co.getColumnLength());
                    tableFieldExcelModel.setDataScale(co.getDecimalDigits());
                    tableFieldExcelModel.setCheck(co.getNullable());
                    tableFieldExcelModel.setPk(co.getPrimaryKey());
                    tableFieldExcelModel.setDefaultValue(co.getColumnDef());
                    tableFieldExcelModel.setRemark(co.getRemarks());
                    tableFieldExcelModels.add(tableFieldExcelModel);
                });
                // 第二次写如也会创建头，然后在第一次的后面写入数据
                finalExcelWriter.write(tableFieldExcelModels, writeSheet1, writeTable4);
            });
            
        } finally {
            // 千万别忘记finish 会帮忙关闭流
            if (excelWriter != null) {
                excelWriter.finish();
            }
        }
    }
    
    @Override
    public void produce(DataModel info, String docName) throws ProduceException {
        try {
            // create file
            File file = getFile(docName);
            // writer freemarker
            try (OutputStream out = new FileOutputStream(file)) {
                // process
                produce(info, out);
                // open the output directory
                openOutputDir();
            }
        } catch (IOException e) {
            throw ExceptionUtils.mpe(e);
        }
    }
    
    
    /**
     * 内部类 实现对单元格的监听
     */
    public class CustomCellWriteHandler implements CellWriteHandler {
        
        @Override
        public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Head head, Integer integer, Integer integer1, Boolean aBoolean) {
        
        }
        
        @Override
        public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, Head head, Integer integer, Boolean aBoolean) {
        
        }
        
        @Override
        public void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, CellData cellData, Cell cell, Head head, Integer integer, Boolean aBoolean) {
        
        }
        
        @Override
        public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<CellData> list, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
            // 这里可以对cell进行任何操作
            if(writeSheetHolder.getSheetName().equals("目录") && !isHead) {
                if(cell.getColumnIndex() == 1) { // 表名 添加超链接
                    CreationHelper createHelper = writeSheetHolder.getSheet().getWorkbook().getCreationHelper();
                    Hyperlink hyperlink = createHelper.createHyperlink(HyperlinkType.DOCUMENT);
                    hyperlink.setAddress("#"+cell.getStringCellValue()+"!A1");
                    cell.setHyperlink(hyperlink);
                    XSSFFont font = (XSSFFont) writeSheetHolder.getSheet().getWorkbook().createFont();
                    font.setColor(IndexedColors.LIGHT_BLUE.getIndex());
                    cell.getCellStyle().setFont(font);
                }
                //log.info("第{}行，第{}列写入完成。", cell.getRowIndex(), cell.getColumnIndex());
            }
        }
    }
    
    /**
    * 自适应列宽
    * @author fanglei
    * @date 2021/08/11
    */
    public class CustomColumnWidthhandler extends AbstractColumnWidthStyleStrategy {
        
        private static final int MAX_COLUMN_WIDTH = 255;
        private  Map<Integer, Map<Integer, Integer>> CACHE = new HashMap(8);
        
        public CustomColumnWidthhandler() {
        }
        
        @Override
        protected void setColumnWidth(WriteSheetHolder writeSheetHolder, List<CellData> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
            boolean needSetWidth = isHead || !CollectionUtils.isEmpty(cellDataList);
            if (needSetWidth) {
                Map<Integer, Integer> maxColumnWidthMap = (Map)CACHE.get(writeSheetHolder.getSheetNo());
                if (maxColumnWidthMap == null) {
                    maxColumnWidthMap = new HashMap(16);
                    CACHE.put(writeSheetHolder.getSheetNo(), maxColumnWidthMap);
                }
                
                Integer columnWidth = this.dataLength(cellDataList, cell, isHead);
                if (columnWidth >= 0) {
                    if (columnWidth > 255) {
                        columnWidth = 255;
                    }
                    
                    Integer maxColumnWidth = (Integer)((Map)maxColumnWidthMap).get(cell.getColumnIndex());
                    if (maxColumnWidth == null || columnWidth > maxColumnWidth) {
                        ((Map)maxColumnWidthMap).put(cell.getColumnIndex(), columnWidth);
                        writeSheetHolder.getSheet().setColumnWidth(cell.getColumnIndex(), columnWidth * 256);
                    }
                    
                }
            }
        }
        
        private Integer dataLength(List<CellData> cellDataList, Cell cell, Boolean isHead) {
            if (isHead) {
                return cell.getStringCellValue().getBytes().length;
            } else {
                CellData cellData = (CellData)cellDataList.get(0);
                CellDataTypeEnum type = cellData.getType();
                if (type == null) {
                    return -1;
                } else {
                    switch(type) {
                        case STRING:
                            return cellData.getStringValue().getBytes().length;
                        case BOOLEAN:
                            return cellData.getBooleanValue().toString().getBytes().length;
                        case NUMBER:
                            return cellData.getNumberValue().toString().getBytes().length;
                        default:
                            return -1;
                    }
                }
            }
        }
    }
    
}
